package com.example.autocode.service;

import com.example.autocode.model.Column;
import com.example.autocode.model.Config;
import com.example.autocode.model.Table;
import com.example.autocode.utils.DataSourceUtils;
import com.example.autocode.utils.FreeMarkerUtils;
import com.google.common.base.CaseFormat;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.springframework.stereotype.Service;

import java.io.*;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Service
public class AutoCodeService {

    Configuration FMConfig = FreeMarkerUtils.createConfig();
    private static final int BUFFER_SIZE = 2 * 1024;

    private String convertType(String type) {
        if(type.equals("VARCHAR") || type.equals("TEXT") || type.equals("CHAR"))
            return "String";
        if(type.equals("INT") || type.equals("INT UNSIGNED")) {
            return "Integer";
        }
        if(type.equals("DATETIME") || type.equals("TIMESTAMP")) {
            return "Date";
        }
        if(type.equals("BIGINT")) {
            return "Long";
        }
        if(type.equals("DOUBLE") || type.equals("DECIMAL")) {
            return "Double";
        }
        if(type.equals("BIT")) {
            return "Boolean";
        }
        return null;
    }

    public String createAutoCode(Config config) {

        UUID uuid = UUID.randomUUID();

        String basePath = "tmp/autoCode_" + new Date().getTime();
        config.setStorageName(basePath);

        try {
            Template modelTemplate = FMConfig.getTemplate("Model.java.ftl");
            Template mapperXmlTemplate = FMConfig.getTemplate("Mapper.xml.ftl");
            Template mapperJavaTemplate = FMConfig.getTemplate("Mapper.java.ftl");
            Template serviceTemplate = FMConfig.getTemplate("Service.java.ftl");
            Template serviceImplTemplate = FMConfig.getTemplate("ServiceImpl.java.ftl");
            Template controllerTemplate = FMConfig.getTemplate("Controller.java.ftl");
            Template RTemplate = FMConfig.getTemplate("R.java.ftl");
            Template SwaggerConfigurationTemplate = FMConfig.getTemplate("SwaggerConfiguration.java.ftl");

            Connection connection = DataSourceUtils.getConnection();
            DatabaseMetaData metaData = connection.getMetaData();
            String path = config.getStorageName() + "/" + config.getPackageName().replace(".", "/");

            for(Table table : config.getTableList()) {
                table.setPackageName(config.getPackageName());

                ResultSet columns = metaData.getColumns(connection.getCatalog(), null, table.getTableName(), null);
                ResultSet primaryKeys = metaData.getPrimaryKeys(connection.getCatalog(), null, table.getTableName());
                Map<String, Boolean> map = new HashMap<>();
                while(primaryKeys.next()) {
                    map.put(primaryKeys.getString("COLUMN_NAME"), true);
                }

                List<Column> columnList = new ArrayList<>();
                List<Column> primaryKeyList = new ArrayList<>();

                while (columns.next()) {
                    String column_name = columns.getString("COLUMN_NAME");
                    String column_type = convertType(columns.getString("TYPE_NAME"));
                    String column_remark = columns.getString("REMARKS");
                    Column column = new Column();
                    column.setColumnName(column_name);
                    column.setType(column_type);
                    column.setRemark(column_remark);
                    column.setPropertyName(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, column_name));
                    if(map.get(column_name) != null) {
                        primaryKeyList.add(column);
                    } else {
                        columnList.add(column);
                    }
                }
                table.setPrimaryKeys(primaryKeyList);
                table.setColumns(columnList);

                generate(modelTemplate, table,  path + "/model/", table.getModelName() + ".java");
                generate(mapperXmlTemplate, table, path + "/mapper/", table.getMapperName() + ".xml");
                generate(mapperJavaTemplate, table, path + "/mapper/", table.getMapperName() + ".java");
                generate(serviceTemplate, table, path + "/service/", table.getServiceName() + ".java");
                generate(serviceImplTemplate, table, path + "/service/impl/", table.getServiceImplName() + ".java");
                generate(controllerTemplate, table, path + "/controller/", table.getControllerName() + ".java");
            }
            generate(RTemplate, config, path + "/common/", "R.java");
            generate(SwaggerConfigurationTemplate, config, path + "/common/", "SwaggerConfiguration.java");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return basePath;
    }

    private void generate(Template template, Object obj, String path, String fileName) {

        String filePath = path + fileName;

        try {
            File dir = new File(path);

            if(!dir.exists()) {
                dir.mkdirs();
            }

            OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath));
            template.process(obj, osw);

        } catch (Exception ex) {
            System.out.println("can not create " + filePath);
            ex.printStackTrace();
        } finally {
            System.out.println("create " + filePath);
        }

    }

    public void write(String basePath, OutputStream outputStream) throws Exception {
        ZipOutputStream zos = new ZipOutputStream(outputStream);
        File sourceFile = new File(basePath);
        compress(sourceFile, zos, sourceFile.getName(), true);
    }

    /**
     * 递归压缩方法
     * @param sourceFile 源文件
     * @param zos		 zip输出流
     * @param name		 压缩后的名称
     * @param KeepDirStructure  是否保留原来的目录结构,true:保留目录结构;
     * 							false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
     * @throws Exception
     */
    private static void compress(File sourceFile, ZipOutputStream zos, String name,
                                 boolean KeepDirStructure) throws Exception{
        byte[] buf = new byte[BUFFER_SIZE];
        if(sourceFile.isFile()){
            // 向zip输出流中添加一个zip实体，构造器中name为zip实体的文件的名字
            zos.putNextEntry(new ZipEntry(name));
            // copy文件到zip输出流中
            int len;
            FileInputStream in = new FileInputStream(sourceFile);
            while ((len = in.read(buf)) != -1){
                zos.write(buf, 0, len);
            }
            // Complete the entry
            zos.closeEntry();
            in.close();
        } else {
            File[] listFiles = sourceFile.listFiles();
            if(listFiles == null || listFiles.length == 0){
                // 需要保留原来的文件结构时,需要对空文件夹进行处理
                if(KeepDirStructure){
                    // 空文件夹的处理
                    zos.putNextEntry(new ZipEntry(name + "/"));
                    // 没有文件，不需要文件的copy
                    zos.closeEntry();
                }

            }else {
                for (File file : listFiles) {
                    // 判断是否需要保留原来的文件结构
                    if (KeepDirStructure) {
                        // 注意：file.getName()前面需要带上父文件夹的名字加一斜杠,
                        // 不然最后压缩包中就不能保留原来的文件结构,即：所有文件都跑到压缩包根目录下了
                        compress(file, zos, name + "/" + file.getName(),KeepDirStructure);
                    } else {
                        compress(file, zos, file.getName(),KeepDirStructure);
                    }

                }
            }
        }
    }

    public void deleteDir(String basePath) {
    }
}
